<?php

/**
 * Rights authorization manager class file.
 *
 * @author Christoffer Niska <cniska@live.com>
 * @copyright Copyright &copy; 2010 Christoffer Niska
 * @since 0.9.7
 */
class RDbAuthManager extends CDbAuthManager {

    /**
     * @var string the name of the rights table.
     */
    public $itemTable = '{{auth_item}}';

    /**
     * @var string the name of the table storing authorization item hierarchy. Defaults to 'AuthItemChild'.
     */
    public $itemChildTable = '{{auth_item_child}}';

    /**
     * @var string the name of the table storing authorization item assignments. Defaults to 'AuthAssignment'.
     */
    public $assignmentTable = '{{auth_assignment}}';
    private $_items = array();
    private $_itemChildren = array();

    /**
     * Adds an item as a child of another item.
     * Overloads the parent method to make sure that
     * we do not add already existing children.
     * @param string $itemName the item name.
     * @param string $childName the child item name.
     * @throws CException if either parent or child doesn't exist or if a loop has been detected.
     */
    public function addItemChild($itemName, $childName) {
        // Make sure that the item doesn't already have this child.


        $hasChild = $this->hasItemChild($itemName, $childName);
        if ($hasChild === false)
            return parent::addItemChild($itemName, $childName);
    }

    /**
     * Assigns an authorization item to a user making sure that
     * the user doesn't already have this assignment.
     * Overloads the parent method to make sure that
     * we do not assign already assigned items.
     * @param string $itemName the item name.
     * @param mixed $userId the user ID (see {@link IWebUser::getId})
     * @param string $bizRule the business rule to be executed when {@link checkAccess} is called
     * for this particular authorization item.
     * @param mixed $data additional data associated with this assignment.
     * @return CAuthAssignment the authorization assignment information.
     * @throws CException if the item does not exist or if the item has already been assigned to the user.
     */
    public function assign($itemName, $userId, $bizRule = null, $data = null) {
        // Make sure that this user doesn't already have this assignment.
        if ($this->getAuthAssignment($itemName, $userId) === null)
            return parent::assign($itemName, $userId, $bizRule, $data);
    }

    /**
     * Returns the authorization item with the specified name.
     * Overloads the parent method to allow for runtime caching.
     * @param string $name the name of the item.
     * @param boolean $allowCaching whether to accept cached data.
     * @return CAuthItem the authorization item. Null if the item cannot be found.
     */
    public function getAuthItem($name, $allowCaching = true) {
        // Get all items if necessary and cache them.
        if ($allowCaching && $this->_items === array())
            $this->_items = $this->getAuthItems();

        // Get the items from cache if possible.
        if ($allowCaching && isset($this->_items[$name])) {
            return $this->_items[$name];
        }
        // Attempt to get the item.
        else if (($item = parent::getAuthItem($name)) !== null) {
            return $item;
        }

        // Item does not exist.
        return null;
    }

    /**
     * Returns the specified authorization items.
     * @param array $names the names of the authorization items to get.
     * @param boolean $nested whether to nest the items by type.
     * @return array the authorization items.
     */
    public function getAuthItemsByNames($names, $nested = false) {
        // Get all items if necessary and cache them.
        if ($this->_items === array())
            $this->_items = $this->getAuthItems();

        // Collect the items we want.
        $items = array();
        foreach ($this->_items as $name => $item) {
            if (in_array($name, $names)) {
                if ($nested === true)
                    $items[$item->getType()][$name] = $item;
                else
                    $items[$name] = $item;
            }
        }

        return $items;
    }

//    /**
//     * Returns the authorization items of the specific type and user.
//     * Overloads the parent method to allow for sorting.
//     * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
//     * meaning returning all items regardless of their type.
//     * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
//     * they are not assigned to a user.
//     * @param boolean $sort whether to sort the items according to their weights.
//     * @return array the authorization items of the specific type.
//     */
//    public function getAuthItems($type = null, $userId = null, $sort = true) {
//        // We need to sort the items.
//        if ($sort === true) {
//            if ($type === null && $userId === null) {
//                $sql = "SELECT name,t1.type,description,t1.bizrule,t1.data,weight
//					FROM {$this->itemTable} t1
//					LEFT JOIN {$this->rightsTable} t2 ON name=itemname
//					ORDER BY t1.type DESC, weight ASC";
//                $command = $this->db->createCommand($sql);
//            } else if ($userId === null) {
//                $sql = "SELECT name,t1.type,description,t1.bizrule,t1.data,weight
//					FROM {$this->itemTable} t1
//					LEFT JOIN {$this->rightsTable} t2 ON name=itemname
//					WHERE t1.type=:type
//					ORDER BY t1.type DESC, weight ASC";
//                $command = $this->db->createCommand($sql);
//                $command->bindValue(':type', $type);
//            } else if ($type === null) {
//                $sql = "SELECT name,t1.type,description,t1.bizrule,t1.data,weight
//					FROM {$this->itemTable} t1
//					LEFT JOIN {$this->assignmentTable} t2 ON name=t2.itemname
//					LEFT JOIN {$this->rightsTable} t3 ON name=t3.itemname
//					WHERE userid=:userid
//					ORDER BY t1.type DESC, weight ASC";
//                $command = $this->db->createCommand($sql);
//                $command->bindValue(':userid', $userId);
//            } else {
//                $sql = "SELECT name,t1.type,description,t1.bizrule,t1.data,weight
//					FROM {$this->itemTable} t1
//					LEFT JOIN {$this->assignmentTable} t2 ON name=t2.itemname
//					LEFT JOIN {$this->rightsTable} t3 ON name=t3.itemname
//					WHERE t1.type=:type AND userid=:userid
//					ORDER BY t1.type DESC, weight ASC";
//                $command = $this->db->createCommand($sql);
//                $command->bindValue(':type', $type);
//                $command->bindValue(':userid', $userId);
//            }
//
//            $items = array();
//            foreach ($command->queryAll() as $row)
//                $items[$row['name']] = new CAuthItem($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], unserialize($row['data']));
//        }
//        // No sorting required.
//        else {
//            $items = parent::getAuthItems($type, $userId);
//        }
//
//        return $items;
//    }

    /**
     * Returns the authorization items of the specific type and user.
     * Overloads the parent method to allow for sorting.
     * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
     * meaning returning all items regardless of their type.
     * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
     * they are not assigned to a user.
     * @param boolean $sort whether to sort the items according to their weights.
     * @return array the authorization items of the specific type.
     */
    public function getAuthItems($type = null, $userId = null, $sort = true) {
        // We need to sort the items.
        if ($sort === true) {
            if ($this->db == null)
                $this->db = $this->getDbConnection();

            if ($type === null && $userId === null) {
                $sql = "SELECT name,t1.type,description,t1.bizrule,t1.data FROM {$this->itemTable} t1 ORDER BY t1.type DESC";
                $command = $this->db->createCommand($sql);
            } else if ($userId === null) {
                $sql = "SELECT name,t1.type,description,t1.bizrule,t1.data 
                    FROM {$this->itemTable} t1 
                    LEFT JOIN {$this->rightsTable} t2 ON name=itemname 
                    WHERE t1.type=:type ORDER BY t1.type DESC";
                $command = $this->db->createCommand($sql);
                $command->bindValue(':type', $type);
            } else if ($type === null) {
                $sql = "SELECT name,t1.type,description,t1.bizrule,t1.data
					FROM {$this->itemTable} t1
					LEFT JOIN {$this->assignmentTable} t2 ON name=t2.itemname
					WHERE userid=:userid
					ORDER BY t1.type DESC";
                $command = $this->db->createCommand($sql);
                $command->bindValue(':userid', $userId);
            } else {
                $sql = "SELECT name,t1.type,description,t1.bizrule,t1.data
					FROM {$this->itemTable} t1
					LEFT JOIN {$this->assignmentTable} t2 ON name=t2.itemname
					WHERE t1.type=:type AND userid=:userid
					ORDER BY t1.type DESC";
                $command = $this->db->createCommand($sql);
                $command->bindValue(':type', $type);
                $command->bindValue(':userid', $userId);
            }

            $items = array();
            foreach ($command->queryAll() as $row)
                $items[$row['name']] = new CAuthItem($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], unserialize($row['data']));
        }
        // No sorting required.
        else {
            $items = parent::getAuthItems($type, $userId);
        }

        return $items;
    }

//	/**
//	 * Returns the children of the specified item.
//	 * Overloads the parent method to allow for caching.
//	 * @param mixed $names the parent item name. This can be either a string or an array.
//	 * The latter represents a list of item names (available since version 1.0.5).
//	 * @param boolean $allowCaching whether to accept cached data.
//	 * @return array all child items of the parent
//	 */
//	public function getItemChildren($names, $allowCaching=true)
//	{
//		// Resolve the key for runtime caching.
//		$key = $names===(array)$names ? implode('|', $names) : $names;
//
//		// Get the children from cache if possible.
//		if( $allowCaching && isset($this->_itemChildren[ $key ])===true )
//		{
//			return $this->_itemChildren[ $key ];
//		}
//		// Children not cached or cached data is not accepted.
//		else
//		{
//			// We only have one name.
//			if( is_string($names) )
//			{
//				$condition = 'parent='.$this->db->quoteValue($names);
//			}
//			// We have multiple names.
//			else if( $names===(array)$names && $names!==array() )
//			{
//				foreach($names as &$name)
//					$name=$this->db->quoteValue($name);
//
//				$condition = 'parent IN ('.implode(', ', $names).')';
//			}
//			else
//			{
//				$condition = '1';
//			}
//
//			$sql = "SELECT name, type, description, bizrule, data
//				FROM {$this->itemTable}, {$this->itemChildTable}
//				WHERE {$condition} AND name=child";
//			$children = array();
//			foreach( $this->db->createCommand($sql)->queryAll() as $row )
//			{
//				if( ($data = @unserialize($row['data']))===false )
//					$data = null;
//
//				$children[ $row['name'] ] = new CAuthItem($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data);
//			}
//
//			// Attach the authorization item behavior.
//			$children = Rights::getAuthorizer()->attachAuthItemBehavior($children);
//
//			// Cache the result.
//			return $this->_itemChildren[ $key ] = $children;
//		}
//	}

    public function getAssignmentsByItemName($name) {
        $sql = "SELECT * FROM {$this->assignmentTable} WHERE itemname=:itemname";
        $command = $this->db->createCommand($sql);
        $command->bindValue(':itemname', $name);

        $assignments = array();
        foreach ($command->queryAll($sql) as $row) {
            if (($data = @unserialize($row['data'])) === false)
                $data = null;

            $assignments[$row['userid']] = new CAuthAssignment($this, $row['itemname'], $row['userid'], $row['bizrule'], $data);
        }

        return $assignments;
    }

//    /**
//     * Updates the authorization items weight.
//     * @param array $result the result returned from jui-sortable.
//     */
//    public function updateItemWeight($result) {
//        foreach ($result as $weight => $itemname) {
//            $sql = "SELECT COUNT(*) FROM {$this->rightsTable}
//				WHERE itemname=:itemname";
//            $command = $this->db->createCommand($sql);
//            $command->bindValue(':itemname', $itemname);
//
//            // Check if the item already has a weight.
//            if ($command->queryScalar() > 0) {
//                $sql = "UPDATE {$this->rightsTable}
//					SET weight=:weight
//					WHERE itemname=:itemname";
//                $command = $this->db->createCommand($sql);
//                $command->bindValue(':weight', $weight);
//                $command->bindValue(':itemname', $itemname);
//                $command->execute();
//            }
//            // Item does not have a weight, insert it.
//            else {
//                if (($item = $this->getAuthItem($itemname)) !== null) {
//                    $sql = "INSERT INTO {$this->rightsTable} (itemname, type, weight)
//						VALUES (:itemname, :type, :weight)";
//                    $command = $this->db->createCommand($sql);
//                    $command->bindValue(':itemname', $itemname);
//                    $command->bindValue(':type', $item->getType());
//                    $command->bindValue(':weight', $weight);
//                    $command->execute();
//                }
//            }
//        }
//    }

    /**
     * Return the roles assigned to a user or all the roles if no userid is provided
     * @param string $userid The id of the user
     * @return array An array of roles(AuthItems) assigned to the user
     */
    public static function getUserAssignedRoles($userid) {
        $assigned = new CDbCriteria();
        $assigned->join = 'LEFT JOIN ' . AuthAssignment::model()->tableName() . ' a ON name = a.itemname';
        if ($userid) {
            $assigned->condition = "type = " . AuthorizationType::ROLE . " AND userid= '" . $userid . "'";
        } else {
            $assigned->condition = "type = " . AuthorizationType::ROLE;
        }
        $assigned->order = "name ASC";
        $assigned = AuthItem::model()->findAll($assigned);
        return ($assigned === null) ? array() : $assigned;
    }

    /**
     * Gets the roles that are not assigned to the user by getting all the roles and
     * removes those assigned to the user, or all the roles if no user id is provided
     * @param String $userid The user's id
     * @return array An array of roles(AuthItems) not assigned to the user
     */
    public static function getUserNotAssignedRoles($userid) {
        $roles = new CDbCriteria();
        $roles->condition = "type=" . AuthorizationType::ROLE;
        $roles->order = "name ASC";
        $final = array();
        if ($userid) {
            $na = AuthItem::model()->findAll($roles);
        } else {
            return AuthItem::model()->findAll($roles);
        }
        $as = self::getUserAssignedRoles($userid);
        foreach ($na as $n) {
            $exists = false;
            foreach ($as as $a) {
                if ($a['name'] == $n['name']) {
                    $exists = true;
                }
            }
            if (!$exists) {
                $final[] = $n;
            }
        }
        return ($final === null) ? array() : $final;
    }

    /**
     * Return the tasks assigned to a role or all the tasks if no role is provided
     * @param string $name The name of the role
     * @return array An array of tasks(AuthItems) assigned to the role
     */
    public static function getRoleAssignedTasks($name) {
        $tasks = new CDbCriteria();
        if ($name) {
            $tasks->condition = "type=" . AuthorizationType::TASK . " AND parent ='" . $name . "'";
            $tasks->join = 'left join ' . Yii::app()->authManager->itemChildTable . ' on name = child';
        } else {
            $tasks->condition = "type=" . AuthorizationType::TASK;
        }
        $tasks->order = "name ASC";
        $assigned = AuthItem::model()->findAll($tasks);

        return ($assigned === null) ? array() : $assigned;
    }

    /**
     * Return the tasks not assigned to a role by getting all the tasks and
     * removing those assigned to the role, or all the tasks if no role is provided
     * @param string $name The name of the role
     * @return array An array of tasks(AuthItems) not assigned to the role
     */
    public static function getRoleNotAssignedTasks($name) {
        $tasks = new CDbCriteria();
        $tasks->condition = "type=" . AuthorizationType::TASK;
        $tasks->order = "name ASC";
        $final = array();
        if ($name) {
            $na = AuthItem::model()->findAll($tasks);
        } else {
            return AuthItem::model()->findAll($tasks);
        }
        $as = self::getRoleAssignedTasks($name);
        foreach ($na as $n) {
            $exists = false;
            foreach ($as as $a) {
                if ($a['name'] == $n['name']) {
                    $exists = true;
                }
            }
            if (!$exists) {
                $final[] = $n;
            }
        }
        return ($final === null) ? array() : $final;
    }

    /**
     * Return the operations assigned to a task or all the operations if no task
     * is provided
     * @param string $name The name of the task
     * @param boolean $clever Use clever Assigning
     * @return array An array of operations(AuthItems) assigned to the task
     */
    public static function getTaskAssignedOpers($name, $clever = false) {
        $tasks = new CDbCriteria();
        if ($name) {
            $tasks->condition = "type=" . AuthorizationType::OPERATION . " AND parent ='" . $name . "'";
            $tasks->join = 'left join ' . Yii::app()->authManager->itemChildTable . ' on name = child';
            if ($clever) {
                $tasks->addCondition("SUBSTR(child,0,3) = SUBSTR('" . $name . "',0,3)");
            }
        } else {
            $tasks->condition = "type=" . AuthorizationType::OPERATION;
        }
        $tasks->order = "name ASC";
        $assigned = AuthItem::model()->findAll($tasks);

        return ($assigned === null) ? array() : $assigned;
    }

    /**
     * Return the operations not assigned to a task by getting all the operations
     * and removing those assigned to the task, or all the operations if no task
     * is provided
     * @param string $name The name of the task
     * @param boolean $clever Use clever Assigning
     * @return array An array of operations(AuthItems) not assigned to the task
     */
    public static function getTaskNotAssignedOpers($name, $clever = false) {
        $tasks = new CDbCriteria();
        $tasks->condition = "type=" . AuthorizationType::OPERATION;
        if ($clever) {
            $tasks->addCondition("SUBSTR(name,0,3) = SUBSTR('" . $name . "',0,3)");
        }
        $final = array();
        if ($name) {
            $na = AuthItem::model()->findAll($tasks);
        } else {
            return AuthItem::model()->findAll($tasks);
        }
        $as = self::getTaskAssignedOpers($name, $clever);
        foreach ($na as $n) {
            $exists = false;
            foreach ($as as $a) {
                if ($a['name'] == $n['name']) {
                    $exists = true;
                }
            }
            if (!$exists) {
                $final[] = $n;
            }
        }

        return ($final === null) ? array() : $final;
    }



}
